/**
 * @file Utils.java
 *
 * Utility class for the EGNOS SDK.
 * 
 * Author: DKE Aerospace Germany GmbH
 *
 * Copyright 2011 European Commission
 *
 * Licensed under the EUPL, Version 1.1 only (the "Licence");
 * You may not use this work except in compliance with the
 * Licence.
 * You may obtain a copy of the Licence at:
 * http://ec.europa.eu/idabc/eupl
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the Licence is
 * distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 * See the Licence for the specific language governing
 * permissions and limitations under the Licence.
 *
 **/
package com.dkeaerospace.egnosdemoapp;

import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.Locale;

import android.content.Context;
import android.os.Handler;
import android.util.Log;

/**
 * Utility class for the EGNOS SDK.
 **/
public class Utils {
    static GlobalState gs;
    Context context;
    FileWriter gpxwriter;
    FileWriter internalLogFilewriter;
    LogFiles log;
    static Handler errorhandler_;
    private static final String TAG = "EGNOS-SDK";

    public Utils( Context context, FileWriter gpxwriter, FileWriter internalLogFilewriter ) {
        this.context = context;
        this.gpxwriter = gpxwriter;
        gs = GlobalState.getInstance();
        log = new LogFiles(gpxwriter, internalLogFilewriter);
    }

    /**
     * get_type function 
     * The function that gets the message type from the egnos subframe.
     * 
     * @param  sfr         the egnos subframe.
     * @return The message type from the egnos subframe.
     */
    public static int get_type( String sfr ) {
        String tmp;
        tmp = extract(sfr, 8, 13);
        return (int) bin2dec(tmp);
    }

    /**
     * get_bandId18 function 
     * The function that gets the band id for Message 18 from the egnos subframe.
     * 
     * @param  sfr         the egnos subframe.
     * @return The band id for Message 18 from the egnos subframe.
     */
    public static int get_bandId18( String sfr ) {
        String tmp;
        // Band ID
        tmp = extract(sfr, 24, 27);
        return (int) bin2dec(tmp);
    }

    /**
     * get_bandId26 function 
     * The function that gets the band id for Message 26 from the egnos subframe.
     * 
     * @param  sfr         the egnos subframe.
     * @return The band id for Message 26 from the egnos subframe.
     */
    public static int get_bandId26( String sfr ) {
        String tmp;
        // Band ID
        tmp = extract(sfr, 20, 23);
        return (int) bin2dec(tmp);
    }

    /**
     * get_blockId26 function  
     * The function that gets the block id for Message 26 from the egnos subframe.
     * 
     * @param  sfr         the egnos subframe.
     * @return The block id for Message 26 from the egnos subframe.
     */
    public static int get_blockId26( String sfr ) {
        String tmp;
        // Band ID
        tmp = extract(sfr, 24, 27);
        return (int) bin2dec(tmp);
    }

    /**
     * bin2dec function 
     * Binary to decimal conversion
     * 
     * @param binary    binary string to convert
     * @return sum      The converted decimal value
     */
    public static long bin2dec( String binary ) {
        long sum = 0;
        sum = Long.parseLong(binary, 2);
        return sum;
    }

    /**
     * extract function 
     * Extract characters between the positions begin and end
     * 
     * @param c          string to be extracted
     * @param begin      Begin position of the chain to be extract
     * @param end        End position of the chain to be extract
     * @return result    The extracted string
     */
    public static String extract( String c, int begin, int end ) {
        String result = null;
        result = c.substring(begin, end + 1);
        return result;
    }

    /**
     * toHex function 
     * Converts an array of bytes to its corresponding hexadecimal string.
     * 
     * @param data     Byte array from the Bluetooth receiver.
     * @return buf     A string which contains the converted hexadecimal values.
     **/
    public static String toHex( byte[] data ) {
        String HEX_DIGITS = "0123456789abcdef";
        StringBuffer buf = new StringBuffer();
        for( int i = 0; i != data.length; i++ ) {
            int v = data[i] & 0xff;
            buf.append(HEX_DIGITS.charAt(v >> 4));
            buf.append(HEX_DIGITS.charAt(v & 0xf));
        }
        return buf.toString();
    }

    /**
     * toHexCharArray function 
     * Converts an array of bytes to its corresponding
     * hexadecimal string. This is an alternative function to toHex.
     * 
     * @param input      Byte array from the Bluetooth receiver.
     * @return output    A string which contains the converted hexadecimal values.
     **/
    public static char[] toHexCharArray( byte[] input ) {
        int m = input.length;
        int n = 2 * m;
        int l = 0;
        char[] output = new char[n];
        for( int k = 0; k < m; k++ ) {
            byte v = input[k];
            int i = (v >> 4) & 0xf;
            output[l++] = (char) (i >= 10 ? ('a' + i - 10) : ('0' + i));
            i = v & 0xf;
            output[l++] = (char) (i >= 10 ? ('a' + i - 10) : ('0' + i));
        }

        return output;
    }

    /**
     * tofifthDecimalPoint function 
     * Rounds off a double value to its decimal point.
     * 
     * @param tow         The double value to round off.
     * @return gpsTime    The rounded double value.
     **/
    public static double toDecimalPoint( double tow ) {
        double gpsTime;
        gpsTime = Double.valueOf(String.format(Locale.US, "%1$.0f", tow));
        return gpsTime;
    }

    /**
     * hexToFloat function 
     * Converts hexadecimal value to a float.
     * 
     * @param value      The hexadecimal value to convert.
     * @param div        The divisor.
     * @return f         The converted float value.
     **/
    public float hexToFloat( String value, int div ) {
        float f;
        int i = 0;

        try {
            i = Integer.parseInt(value, 16);
        } catch (NumberFormatException e) {
            i = Integer.parseInt(value);
        }

        f = (float) i / div;
        return f;
    }

    /**
     * toSinglePrecisionFloat function 
     * Convert hexadecimal to single precision float.
     * 
     * @param substring     Hexadecimal string of message 28 to convert 
     *                      from hex to a single precision float.
     * @return result       Result as a double.
     **/
    public static double toSinglePrecisionFloat( String substring ) {
        double result = 0;

        Long i = new BigInteger(substring, 16).longValue(); // B0B1B2B3
        Float f = Float.intBitsToFloat(i.intValue());
        result = f;

        return result;
    }

    /**
     * toDoublePrecisionFloat function 
     * Convert hexadecimal to double precision  float.
     * 
     * @param substring     Hexadecimal string of message 28 to convert 
     *                      from hex to a double precision float.
     * @return result       Result as a double.
     **/
    public static double toDoublePrecisionFloat( String substring ) { // substring
        // Order:B0B1B2B3B4B5B6B7B8
        double result = 0;
        String resultString = "";
        int length = substring.length();
        // ResultString Order:B4B5B6B7B0B1B2B3
        resultString = substring.substring(8, length) + substring.substring(0, 8);
        Long i = new BigInteger(resultString, 16).longValue();
        result = Double.longBitsToDouble(i);

        return result;
    }

    /**
     * charToString function 
     * Converts a char array to a string.
     * 
     * @param charArray     The character array to be converted.
     * @param start         The start index.
     * @param end           The end index.
     * @return convString   The converted string.
     **/
    public static String charToString( final char[] charArray, final int start, final int end ) {
        String convString = "";

        for( int i = start; i < end + 1; i++ ) {
            convString = convString + Character.toString(charArray[i]);
        }
        return convString;
    }

    /**
     * charToStringUbl function 
     * Converts a char array to string in the reverse order.
     * 
     * @param charArray    The character array to be converted.
     * @param start        The start index.
     * @param end          The end index.
     * @return convString  The converted string.
     **/
    public static String charToStringUbl( final char[] charArray, final int start, final int end ) {
        String convString = "";

        for( int i = end; i > start; i -= 2 ) {
            convString = convString + Character.toString(charArray[i - 1]) + Character.toString(charArray[i]);
        }
        return convString;
    }

    /**
     * generateMessage function 
     * Converts Hexadecimal String to bytes
     * 
     * @param messageHex    Hexadecimal string as e.g A0A20008A6001C01000000000C3B0B3
     * @return message      ArrayList of Hexadecimal strings
     **/
    public static byte[] generateByteMessage( final String messageHex ) {
        int length = messageHex.length() / 2;
        ByteBuffer message = ByteBuffer.allocate(length);
        message.put(new BigInteger(messageHex, 16).toByteArray(), 1, length);
        return message.array();
    }

    /**
     * writeMessageToReceiver function 
     * Write the messages to Bluetooth receiver as bytes.
     * 
     * @param buffer        Message xyz as a an array of bytes.
     * @param gS            Globalstate variable.
     **/
    public static int write( final byte[] buffer, final GlobalState gS ) {
        boolean execute = false;

        try {
            gS.getSocket();
            execute = true;

        } catch (Exception e) {
            Log.e(TAG, "Utils | write() Error: " + e.getMessage());
            e.printStackTrace();
            gS.setSocket(null);
            return -1;
        }

        if (execute) {
            try {
                if (gS.getSocket() != null) {
                    gS.getOutputStream().write(buffer);
                    gS.getOutputStream().flush();
                }
            } catch (IOException e) {
                Log.e(TAG, "Utils | write() Error: " + e.getMessage());
                e.printStackTrace();
                gS.setSocket(null);
                return -1;
            }
            return 1;
        } else
            return -1;
    }

    /**
     * ArrayComparator class 
     * Sort matrix of message 28 in ascending order based on GPS Software Time.
     * 
     * @param messageID28      Matrix of message 28.
     * @return messageID28     Matrix of message 28 sorted in ascending order.
     **/
    class ArrayComparator implements Comparator<Comparable[]> {
        private final int columnToSort; // column to sort
        private final boolean ascending;

        public ArrayComparator( int columnToSort, boolean ascending ) {
            this.columnToSort = columnToSort;
            this.ascending = ascending;
        }

        /**
         * compare class 
         * Compares values of each row based on a specific columnToSort.
         * 
         * @param c1            Row i.
         * @param c2            Row i+1.
         * @return ascending    True if greater, false if smaller.
         **/
        public int compare( Comparable[] c1, Comparable[] c2 ) {
            int cmp = 0;

            try {
                cmp = c1[columnToSort].compareTo(c2[columnToSort]);
            } catch (NullPointerException e) {
                Log.e(TAG, "Utils | Compare Error: " + e.getMessage());
            }
            return ascending ? cmp : -cmp;
        }
    }
}